/* 
GeoGebra - Dynamic Mathematics for Everyone
http://www.geogebra.org

This file is part of GeoGebra.

This program is free software; you can redistribute it and/or modify it 
under the terms of the GNU General Public License as published by 
the Free Software Foundation.

 */

package org.geogebra.common.kernel.statistics;

import java.util.ArrayList;

import org.geogebra.common.kernel.Construction;
import org.geogebra.common.kernel.algos.AlgoElement;
import org.geogebra.common.kernel.commands.Commands;
import org.geogebra.common.kernel.geos.GeoBoolean;
import org.geogebra.common.kernel.geos.GeoElement;
import org.geogebra.common.kernel.geos.GeoList;
import org.geogebra.common.kernel.geos.GeoNumeric;
import org.geogebra.common.kernel.geos.GeoPoint;
import org.geogebra.common.kernel.geos.GeoPolyLine;
import org.geogebra.common.kernel.kernelND.GeoPointND;

/**
 * Creates a frequency polygon.
 * 
 * Input: inputs are identical to AlgoHistogram
 * 
 * Output: PolyLine created from class borders and frequency heights of the
 * histogram generated by the inputs
 * 
 * @author G.Sturr
 */

public class AlgoFrequencyPolygon extends AlgoElement {
	// input
	private GeoList list1;
	private GeoList list2;
	private GeoList list3;
	private GeoBoolean isCumulative;
	private GeoBoolean useDensity;
	private GeoNumeric density;

	private GeoPolyLine outputPolyLine; // output

	private GeoPointND[] points = null;
	private AlgoHistogram algoHistogram;

	private boolean right = false;

	/**
	 * Creates a frequency polygon from two data lists.
	 * 
	 * @param cons
	 *            construction
	 * @param list1
	 *            list of boundaries
	 * @param list2
	 *            list of heights or raw data
	 */
	public AlgoFrequencyPolygon(Construction cons, GeoList list1,
			GeoList list2) {
		this(cons, null, list1, list2, null, null, null);
	}

	/**
	 * /** Creates frequency polygon from two data lists with parameter
	 * specifications.
	 * 
	 * @param cons
	 *            construction
	 * @param isCumulative
	 *            cumulative?
	 * @param list1
	 *            class boundaries
	 * @param list2
	 *            data
	 * @param list3
	 *            frequencies
	 * @param useDensity
	 *            whether to use density
	 * @param density
	 *            density
	 */
	public AlgoFrequencyPolygon(Construction cons, GeoBoolean isCumulative,
			GeoList list1, GeoList list2, GeoList list3, GeoBoolean useDensity,
			GeoNumeric density) {
		super(cons);
		this.list1 = list1;
		this.list2 = list2;
		this.list3 = list3; // optional frequencies
		this.isCumulative = isCumulative;
		this.useDensity = useDensity;
		this.density = density;

		outputPolyLine = new GeoPolyLine(cons, points);

		setInputOutput();
		compute();
	}

	@Override
	public Commands getClassName() {
		return Commands.FrequencyPolygon;
	}

	@Override
	protected void setInputOutput() {

		ArrayList<GeoElement> tempList = new ArrayList<>();

		if (isCumulative != null) {
			tempList.add(isCumulative);
		}
		tempList.add(list1);
		tempList.add(list2);
		if (list3 != null) {
			tempList.add(list3);
		}
		if (useDensity != null) {
			tempList.add(useDensity);
		}
		if (density != null) {
			tempList.add(density);
		}
		input = new GeoElement[tempList.size()];
		input = tempList.toArray(input);

		boolean suppressLabelCreation = cons.isSuppressLabelsActive();
		cons.setSuppressLabelCreation(true);

		algoHistogram = new AlgoHistogram(cons, isCumulative, list1, list2,
				list3, useDensity, density, right);

		cons.setSuppressLabelCreation(suppressLabelCreation);

		setOutput();

		// parent of output
		outputPolyLine.setParentAlgorithm(this);
		cons.addToAlgorithmList(this);

		setDependencies(); // done by AlgoElement
	}

	private void setOutput() {
		setOnlyOutput(outputPolyLine);
	}

	/**
	 * @return frequency polyline
	 */
	public GeoPolyLine getResult() {
		return outputPolyLine;
	}

	@Override
	public final void compute() {

		// update our histogram to get class borders and y values
		algoHistogram.update();

		if (!algoHistogram.getOutput()[0].isDefined()) {
			outputPolyLine.setUndefined();
			return;
		}
		double[] leftBorder = algoHistogram.getLeftBorder();
		if (leftBorder == null || leftBorder.length < 2) {
			outputPolyLine.setUndefined();
			return;
		}
		double[] yValue = algoHistogram.getValues();
		if (yValue == null || yValue.length < 2) {
			outputPolyLine.setUndefined();
			return;
		}

		// if we got this far everything is ok; now define the polyLine
		outputPolyLine.setDefined();

		// remember old number of points
		int oldPointsLength = points == null ? 0 : points.length;

		// create a new point array
		boolean doCumulative = isCumulative != null
				&& isCumulative.getBoolean();
		int size = doCumulative ? yValue.length : yValue.length + 1;
		points = new GeoPoint[size];

		// create points and load the point array

		if (doCumulative) {
			points[0] = new GeoPoint(cons, leftBorder[0], 0.0, 1.0);
			for (int i = 0; i < yValue.length - 1; i++) {
				points[i + 1] = new GeoPoint(cons, leftBorder[i + 1],
						yValue[i], 1.0);
			}
		} else {
			double midpoint = leftBorder[0]
					- 0.5 * (leftBorder[1] - leftBorder[0]);
			points[0] = new GeoPoint(cons, midpoint, 0.0, 1.0);
			for (int i = 0; i < yValue.length - 1; i++) {
				midpoint = 0.5 * (leftBorder[i + 1] + leftBorder[i]);
				points[i + 1] = new GeoPoint(cons, midpoint, yValue[i],
						1.0);
			}
			midpoint = 1.5 * leftBorder[yValue.length - 1]
					- .5 * (leftBorder[yValue.length - 2]);
			points[yValue.length] = new GeoPoint(cons, midpoint, 0.0,
					1.0);
		}

		// update the polyLine
		outputPolyLine.setPoints(points);
		if (oldPointsLength != points.length) {
			setOutput();
		}
	}

}
